package la.renzhen.rtpt.spring.boot.starter;

import com.ecwid.consul.transport.TLSConfig;
import com.netflix.appinfo.ApplicationInfoManager;
import la.renzhen.rtpt.config.ConfigurationProvider;
import la.renzhen.rtpt.config.ConfigurationSource;
import la.renzhen.rtpt.config.ReloadStrategy;
import la.renzhen.rtpt.config.Reloadable;
import la.renzhen.rtpt.config.provider.ComposeConfigurationProvider;
import la.renzhen.rtpt.config.reload.ImmediateReloadStrategy;
import la.renzhen.rtpt.config.reload.PeriodicalReloadStrategy;
import la.renzhen.rtpt.config.source.*;
import la.renzhen.rtpt.config.source.template.DiscoveryRestTemplateSelector;
import la.renzhen.rtpt.config.source.template.RestTemplateSelector;
import la.renzhen.rtpt.config.source.template.SingleRestTemplateSelector;
import la.renzhen.rtpt.spring.beans.ConfigurationScanner;
import la.renzhen.rtpt.spring.boot.RTPTProperties;
import la.renzhen.rtpt.spring.boot.conditional.ConditionalOnPrefixProperty;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 配置内容加载<p>
 *
 * @author <a href="mailto:zhouhaichao@2008.sina.com">haiker</a>
 * @version 2017/7/21 下午1:08
 */
@Slf4j
@Configuration
@EnableConfigurationProperties({RTPTProperties.class})
@ConditionalOnClass({SpringConfigurationSource.class, ReloadStrategy.class, ConfigurationProvider.class})
public class ConfigurationAutoConfiguration {

    @Autowired
    RTPTProperties config;

    @Bean
    @ConditionalOnMissingBean(ReloadStrategy.class)
    public ReloadStrategy reloadable() {
        if (config.getRefreshStrategy() == -1) {
            return null;
        }
        if (config.getRefreshStrategy() == 0) {
            return new ImmediateReloadStrategy();
        }
        return new PeriodicalReloadStrategy(config.getRefreshStrategy(), TimeUnit.SECONDS);
    }

    @Bean
    @SneakyThrows
    @ConditionalOnPrefixProperty(RTPTProperties.Consul.PREFIX)
    public ConfigurationSource consul() {
        HostAndPort hostAndPort = HostAndPort.parseString(config.getConsul().getAddress());
        String prefix = config.getConsul().getFolder();
        TLSConfig tlsConfig = config.getConsul().getTlsConfig();

        return new ConsulConfigurationSource(hostAndPort.getHost(), hostAndPort.getPort(), prefix, tlsConfig);
    }


    @Bean
    @ConditionalOnPrefixProperty(RTPTProperties.Redis.PREFIX)
    public ConfigurationSource redis() {
        RTPTProperties.Redis redisConfig = config.getRedis();

        String module = redisConfig.getFolder();

        String host = redisConfig.getHost();
        int port = redisConfig.getPort();
        if (StringUtils.hasText(redisConfig.getAddress())) {
            HostAndPort hostAndPort = HostAndPort.parseString(redisConfig.getAddress());
            host = redisConfig.getHost();
            port = redisConfig.getPort();
        }
        int timeout = redisConfig.getTimeout();

        JedisPool pool = new JedisPool(jedisPoolConfig(redisConfig.getPool()), host, port, timeout);
        Jedis jedis = new Jedis();
        jedis.select(redisConfig.getDatabase());
        jedis.setDataSource(pool);

        String passwd = redisConfig.getPassword();
        if (StringUtils.hasText(passwd)) {
            Assert.isTrue(jedis.auth(passwd).equalsIgnoreCase("OK"), "redis auth password is invalid");
        }

        return new RedisConfigurationSource(module, jedis);
    }

    private JedisPoolConfig jedisPoolConfig(RTPTProperties.Redis.Pool poolConfig) {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(poolConfig.getMaxActive());
        config.setMaxIdle(poolConfig.getMaxIdle());
        config.setMinIdle(poolConfig.getMinIdle());
        config.setMaxWaitMillis(poolConfig.getMaxWait());
        return config;
    }


    /**
     * 框架提供的注册获取中心，也就是rtpt-admin提供的stora模块的地址
     */
    @ConditionalOnPrefixProperty(RTPTProperties.Center.PREFIX)
    public class CenterConfigurationSource {
        @Bean
        public RestTemplateSelector restTemplateSelector(RTPTProperties config, @Autowired(required = false) DiscoveryClient client) {
            RTPTProperties.Center center = config.getCenter();
            RestTemplateSelector selector = null;

            if (center.getDiscovery().isEnabled()) {
                if (client == null) {
                    throw new NullPointerException("the DiscoveryClient is null");
                }
                selector = new DiscoveryRestTemplateSelector(config.getCenter().getDiscovery().getServiceId(), client);
            } else {
                selector = new SingleRestTemplateSelector(
                        center.getAddress(),
                        center.getUsername(), center.getPassword(),
                        center.getAuthorization(), center.getHeaders()
                );
            }
            return selector;
        }

        @Bean
        public ConfigurationSource centerConfigurationSource(RTPTProperties config, RestTemplateSelector selector) {
            return new AdminCenterSource(config, selector);
        }

        public class DynamicMetadataReporter {
            @Autowired
            private ApplicationInfoManager aim;
            @PostConstruct
            public void init() {
                Map<String, String> metadata = aim.getInfo().getMetadata();
                metadata.put("X-RPTP-CONFIG-TOKEN", UUID.randomUUID().toString().replaceAll("-", ""));
            }
        }
        @Bean
        public DynamicMetadataReporter dynamicMetadataReporter() {
            return new DynamicMetadataReporter();
        }
    }

    @Bean
    @Primary
    @ConditionalOnMissingBean(SpringConfigurationSource.class)
    public SpringConfigurationSource mergeConfigurationSource(
            @Autowired(required = false) List<ConfigurationSource> customerSources,
            @Autowired(required = false) ReloadStrategy reloadStrategy
    ) {
        ConfigurationSource configurationSource = new MergeConfigurationSource(customerSources);
        if (reloadStrategy != null) {
            configurationSource = new CachedConfigurationSource(config.getEnvironment(), config.getPrefix(), configurationSource);
            reloadStrategy.register((Reloadable) configurationSource);
        }
        return new SpringConfigurationSource(configurationSource);
    }

    @Bean
    @ConditionalOnMissingBean(ConfigurationProvider.class)
    public ConfigurationProvider configurationProvider(SpringConfigurationSource source) {
        return new ComposeConfigurationProvider(source, config.getEnvironment(), config.getPrefix());
    }

}
